//
//  MainController.m
//  Simple Tagger
//
//  Created by Thomas Bröckers on 21.02.08.
//  Copyright 2008 __MyCompanyName__. All rights reserved.
//

#import "MainController.h"
#import "Track.h"

NSString* const STRenamingMaskKey = @"LastUserRenamingMask";
NSString* const STStatusChangedNotification = @"STStatusChanged";

@implementation MainController

+ (void) initialize {

    // Register the application defaults
    NSMutableDictionary *defaultValues = [NSMutableDictionary dictionary];
    NSString *renamingMask = @"%n - %t";
    [defaultValues setObject:renamingMask forKey:STRenamingMaskKey];
    [[NSUserDefaults standardUserDefaults] registerDefaults:defaultValues];
            
}

- (void) awakeFromNib {    
    
    // Eigenen Typen für Drag and Drop Operation innerhalb des TableViews erstellen
    #define MyPrivateTableViewDataType @"MyPrivateTableViewDataType"
    #define iTunesPboardType @"CorePasteboardFlavorType 0x6974756E"
    
    // Controls für Drag and Drop Operation registrieren
    [fileListView registerForDraggedTypes:[NSArray arrayWithObjects:NSFilenamesPboardType, 
                                           MyPrivateTableViewDataType, iTunesPboardType, nil]];
    [trackListView registerForDraggedTypes:[NSArray arrayWithObjects:MyPrivateTableViewDataType, nil]];
    
    // Don't know, why the next three lines are in the code, but they prevent Simple Tagger from
    // reacting on the mouse events like scrolling with the mouse wheel. So I commented them out.
    //[fileListView setNextResponder: self];
    //[trackListView setNextResponder: self];
    //[resultTableView setNextResponder: self];

    // Read the user defaults
    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
    [renaming setStringValue:[defaults objectForKey:STRenamingMaskKey]];

    // Register as an observer for status change notification
    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(handleStatusChange:)
                                                 name:STStatusChangedNotification
                                               object:nil];
    
    [self switchStatus:@"Waiting for user input..."];
}

- (void) dealloc {

    [super dealloc];

    // Remove self as a observer
    [[NSNotificationCenter defaultCenter] removeObserver:self];    

}

- (IBAction) getSearchResult:(id)sender {
        
    // Werte der Oberfläche auslesen
    NSString *stringToSearch = [keywords stringValue];
    
    if (![stringToSearch isEqualToString:@""]) {

        // Alte Werte zurücksetzen
        [self clearTableViewsAndDataSources];

        // set new status
        [self switchStatus:@"Starting amazon database searching..."];

        int numberOfSelection = [searchSelection selectedTag];

        NSString *searchEngine = @"";

        // Ausgewählte Suchmaschine bestimmen
        switch (numberOfSelection) {
            
            case 1: {
                searchEngine = @"de";
                break;
            }
            case 2: {
                searchEngine = @"co.uk";
                break;
            }
            case 3: {
                searchEngine = @"fr";
                break;
            }
            case 4: {
                searchEngine = @"com";
                break;
            }
        }
    
        SearchEngine *search = [[SearchEngine alloc] init];
        resultList = [search buildSearchResultFromKeywords:stringToSearch
                                            withEngine:searchEngine];
        [search release];
    
        // Neuladen der TableViews für das Ergebnis und die Tracks
        [self refreshTableView:resultTableView];
        [resultTableView selectRow:0 byExtendingSelection:NO];
    
        // set new status
        NSString *newStatus = [NSString stringWithFormat:@"Finished amazon database searching. %i entries processed.", 
                               [resultList count]];
        [self switchStatus:newStatus];
    }
}

- (IBAction) processTagging:(id)sender {
 
    if (([songs count] > 0) && ([resultList count] > 0)) {

        // set new status
        [self switchStatus:@"Tagging files now..."];        

        [tagButton setEnabled:NO];
    
        int index = ([resultTableView selectedRow] != -1 ? [resultTableView selectedRow] : 0);

        FileHandler *fileHandler = [[FileHandler alloc] init];
    
        // Aktuelles Cover aus dem NSImageView auslesen und speichern, da es sich zwischenzeitlich per
        // Drag and Drop geändert haben kann.
        [[[self resultList] objectAtIndex:index] setCover:[cover image]];
    
        [fileHandler tagFiles:[self songs]
                   WithTracks:[self trackItems]
                    AndResult:[[self resultList] objectAtIndex:index]];
    
        [fileHandler release];
        [tagButton setEnabled:YES];

        // set new status
        [self switchStatus:@"Tagging files finished..."];        

    }
}

- (IBAction) processRenaming:(id) sender {

    // set new status
    [self switchStatus:@"Renaming files now..."];        

    [renameButton setEnabled:NO];
    
    FileHandler *fileHandler = [[FileHandler alloc] init];
    [fileHandler renameFiles:[self songs]
                  WithSchema:[renaming stringValue]];
    [fileHandler release];
    [fileListView reloadData];
    [fileListView setNeedsDisplay:YES];
    
    [renameButton setEnabled:YES];

    // set new status
    [self switchStatus:@"Renaming files finished..."];        

}

- (IBAction) addAlbumInformation:(id) sender {
    [self generateNewAlbumEntryForArtist:[artist stringValue]
                                   Album:[album stringValue]
                                    Year:[publicationYear stringValue]
                                   Genre:[genre stringValue]
                                   Label:[label stringValue] 
                                  Tracks:[tracks stringValue] 
                                   Discs:[discs stringValue]];
}

- (IBAction) addTrackItem:(id) sender {
    [self generateNewTrackItem:[trackTitle stringValue] trackArtist:[trackArtist stringValue]];
}

- (IBAction) dropTags:(id) sender {

    FileHandler *fileHandler = [[FileHandler alloc] init];
    [fileHandler deleteTagsInMp3Files:[self songs]];
    [fileHandler release];
    
}

- (IBAction) clearAll:(id) sender {
    
    [self clearTableViewsAndDataSources];
    
    songs = nil;
    [self refreshTableView:fileListView];
    
    [keywords setStringValue:@""];
    
    // set new status
    [self switchStatus:@"Waiting for user input..."];

}

- (IBAction) clearFiles:(id) sender {
    
    songs = nil;
    [self refreshTableView:fileListView];
    
    // set new status
    [self switchStatus:@"Waiting for user input..."];
    
}

- (int)numberOfRowsInTableView:(NSTableView *) tableView {
    
    if (tableView == fileListView) {
        return [songs count];
    }
    else if (tableView == resultTableView) {
        return [resultList count];
    }
    else if (tableView == trackListView) {
        return [trackItems count];
    }
    else {
        return 0;
    }
}

- (id)              tableView:(NSTableView *) tableView 
    objectValueForTableColumn:(NSTableColumn *) tableColumn 
                          row:(int) row {
    
    id returnObject = nil;
    
    @try {

        if (tableView == fileListView) {
            returnObject = [[songs objectAtIndex:row] lastPathComponent];
        }
        else if (tableView == resultTableView) {
            if ([[tableColumn identifier] isEqualToString:@"artistName"]) {
                returnObject = [[resultList objectAtIndex:row] artistName];
            }
            else if ([[tableColumn identifier] isEqualToString:@"albumName"]) {
                returnObject = [[resultList objectAtIndex:row] albumName];
            }
            else if ([[tableColumn identifier] isEqualToString:@"numberOfTracks"]) {
                returnObject = [NSString stringWithFormat:@"%d", [[resultList objectAtIndex:row] numberOfTracks]];
            }
            else if ([[tableColumn identifier] isEqualToString:@"yearOfPublication"]) {
                returnObject = [[resultList objectAtIndex:row] yearOfPublication];
            }
            else if ([[tableColumn identifier] isEqualToString:@"label"]) {
                returnObject = [[resultList objectAtIndex:row] label];
            }
            else if ([[tableColumn identifier] isEqualToString:@"genre"]) {
                returnObject = [[resultList objectAtIndex:row] genre];
            }
            else if ([[tableColumn identifier] isEqualToString:@"numberOfDiscs"]) {
                returnObject = [NSString stringWithFormat:@"%d", [[resultList objectAtIndex:row] numberOfDiscs]];
            }            
        }
        else if (tableView == trackListView) {
            if ([[tableColumn identifier] isEqualToString:@"Number"]) {
                returnObject = [NSString stringWithFormat:@"%d", (row + 1)];
            }
            else if ([[tableColumn identifier] isEqualToString:@"Title"]) {
                returnObject = [[trackItems objectAtIndex:row] title];
            }
            else {
                returnObject = [[trackItems objectAtIndex:row] artist];
            }
        }
    }
    @catch (NSException *exception) {
        NSLog(@"MainController:tableView:objectValueForTableColumn:row: Caught %@: %@", 
              [exception name], [exception reason]);
    }
    
    return returnObject;
}

- (void) tableView:(NSTableView *) tableView
    setObjectValue:(id) object
    forTableColumn:(NSTableColumn *) tableColumn
               row:(int) row {
    
    if (tableView == trackListView) {
        Track *newTrack = [trackItems objectAtIndex:row];
        if ([[tableColumn identifier] isEqualToString:@"Title"]) {
            newTrack.title = (NSString *) object;
        }
        else {
            // Must be the second identifier "artist"
            newTrack.artist = (NSString *) object;
        }
        [trackItems replaceObjectAtIndex:row withObject:newTrack];
    }
    else if (tableView == resultTableView) {
        ResultEntry *entry = [resultList objectAtIndex:row];
        [entry setValue:object forKey:[tableColumn identifier]];
    }
}

- (void) tableViewSelectionDidChange:(NSNotification *) aNotification {
    
    // Auslesen der Benachrichtigungsquelle (= resultTableView) und der selektierten Zeile
    resultTableView = [aNotification object];
    int index = [resultTableView selectedRow];
    
    // Check first, if there is more than one entry in the result list or if there is only one
    // entry and no rows in the trackListView (then it is the first call, ticket #26). If not, no
    // other row could be selectect and nothing has to change.
    if (([resultList count] > 1) || ([resultList count] == 1 && [trackListView numberOfRows] == 0)) {
        if ([[resultList objectAtIndex:index] trackList] != nil) {
            
            // Detailanzeige löschen
            [self clearDetails];
        
            if (index >= 0) {
                [cover setImage:[[resultList objectAtIndex:index] cover]];
                trackItems = [[resultList objectAtIndex:index] trackList];
            }
        }
        else {
            trackItems = nil;
            [self refreshTableView:trackListView];
            if ([[resultList objectAtIndex:index] cover] == nil) [cover setImage:nil];
        }

        [self refreshTableView:trackListView];

    }
}

- (BOOL)     tableView:(NSTableView *) tv 
  writeRowsWithIndexes:(NSIndexSet *) rowIndexes 
          toPasteboard:(NSPasteboard*) pboard {
    
    NSData *data = [NSKeyedArchiver archivedDataWithRootObject:rowIndexes];
    [pboard declareTypes:[NSArray arrayWithObject:MyPrivateTableViewDataType] owner:self];
    [pboard setData:data forType:MyPrivateTableViewDataType];
    
    return YES;
    
}

- (NSDragOperation) tableView:(NSTableView*) tv 
                 validateDrop:(id <NSDraggingInfo>) info 
                  proposedRow:(int) row 
        proposedDropOperation:(NSTableViewDropOperation) op {
    
    NSString *type = [[info draggingPasteboard] 
                      availableTypeFromArray:[NSArray arrayWithObjects:NSFilenamesPboardType, 
                                              MyPrivateTableViewDataType, iTunesPboardType, nil]];
    
    if ([type isEqualToString:MyPrivateTableViewDataType]) {
        return NSDragOperationMove;
    } else if ([type isEqualToString:NSFilenamesPboardType] || [type isEqualToString:iTunesPboardType]) {
        [tv setDropRow:[tv numberOfRows] dropOperation:NSTableViewDropAbove];
        return NSTableViewDropAbove;
    }
    else {
        return NSDragOperationNone;
    }
}

- (BOOL)    tableView:(NSTableView*) tv 
           acceptDrop:(id <NSDraggingInfo>) info 
                  row:(int) row 
        dropOperation:(NSTableViewDropOperation) op {
    
    NSPasteboard *myPasteboard = [info draggingPasteboard];
    NSArray *typeArray = [NSArray arrayWithObjects:NSFilenamesPboardType, 
                          MyPrivateTableViewDataType, iTunesPboardType, nil];
    NSString *availableType = [myPasteboard availableTypeFromArray:typeArray];

    if ([availableType isEqualToString:NSFilenamesPboardType] || [availableType isEqualToString:iTunesPboardType]) {

        // 1. Variante: MP3 Files bzw. ein Ordner mit Files werden gedroppt
        NSString *droppedItemPath, *fileName;
        int i, j, rowToInsert;
        BOOL isDirectory;
        
        if (songs == nil) {songs = [[NSMutableArray alloc] init];}
        
        NSFileManager *fileManager = [NSFileManager defaultManager];
        
        NSMutableArray *fileArray = [NSMutableArray array];
        if ([availableType isEqualToString:NSFilenamesPboardType]) {
            // Dateien wurden aus dem Finder gedropped
            fileArray = [myPasteboard propertyListForType:availableType];
        }
        else if ([availableType isEqualToString:iTunesPboardType]) {
            // Dateien wurden direkt aus iTunes gedropped
            NSDictionary *iTunesDict = [myPasteboard propertyListForType:availableType];
            NSArray *iTunesTracks = [iTunesDict valueForKey:@"Tracks"];
            NSEnumerator *enumerator = [iTunesTracks objectEnumerator];
            NSDictionary *iTunesTrack = nil;
            NSURL *url = nil;
            while((iTunesTrack = [enumerator nextObject])) {
                url = [NSURL URLWithString:[iTunesTrack valueForKey:@"Location"]];
                if ([url isFileURL]) {
                    NSString *iTunesFileName = [url path];
                    [fileArray addObject:iTunesFileName];
                }
            }
        }
        
        NSArray *droppedItems = [[NSArray alloc] initWithArray:fileArray];
    
        rowToInsert = row;
        for (i = 0;  i < [droppedItems count]; i++) {

            droppedItemPath = [droppedItems objectAtIndex:i];
            
            // Handelt es sich um einen Ordner?
            if ([fileManager fileExistsAtPath:droppedItemPath isDirectory:&isDirectory] && isDirectory) {
                NSArray *fileList = [fileManager directoryContentsAtPath:droppedItemPath];
                for (j = 0; j < [fileList count]; j++) {
                                     
                    fileName = [droppedItemPath stringByAppendingPathComponent:[fileList objectAtIndex:j]];
                    
                    // Im Ordner können sich auch andere Dateien befinden, die ignoriert werden sollen                    
                    if ([[fileName pathExtension] isEqualToString:@"mp3"]) {
                        [songs insertObject:fileName atIndex:rowToInsert];
                        rowToInsert++;
                    }
                }
            } else {
                // Handle mp3-Files only
                if ([[droppedItemPath pathExtension] isEqualToString:@"mp3"]) {
                    [songs insertObject:droppedItemPath atIndex:rowToInsert];
                    rowToInsert++;
                }
            }
        }
        
        // Array sortieren
        [songs sortUsingSelector:@selector(compare:)];
        [droppedItems release];

        [self writeTagsFromFilesInTableViews];
        
    } else if ([availableType isEqualToString:MyPrivateTableViewDataType]) {
        
        // 2. Variante: Zeilen wurden innerhalb der TableView verschoben
        NSData *data = [myPasteboard dataForType:MyPrivateTableViewDataType];
        NSIndexSet *rows = [NSKeyedUnarchiver unarchiveObjectWithData:data];
        NSMutableArray *movedRows = [[NSMutableArray alloc] init];

        NSUInteger theRowNumber = [rows firstIndex];
                
        while (theRowNumber != NSNotFound) {
            if (tv == fileListView) {
                [movedRows addObject:[songs objectAtIndex:theRowNumber]];
                [songs replaceObjectAtIndex:theRowNumber withObject:[NSNull null]];
            }
            else if (tv == trackListView) {
                [movedRows addObject:[trackItems objectAtIndex:theRowNumber]];
                [trackItems replaceObjectAtIndex:theRowNumber withObject:[NSNull null]];                
            }
            theRowNumber = [rows indexGreaterThanIndex: theRowNumber];
        }

        if (tv == fileListView) {
            [songs replaceObjectsInRange:NSMakeRange(row, 0) withObjectsFromArray:movedRows];
            [songs removeObjectIdenticalTo:[NSNull null]];
        }
        else if (tv = trackListView) {
            [trackItems replaceObjectsInRange:NSMakeRange(row, 0) withObjectsFromArray:movedRows];
            [trackItems removeObjectIdenticalTo:[NSNull null]];            
        }
        
        [movedRows release];
    }
        
    [self refreshTableView:tv];
    [self updatePreview];
    
    return YES;
}

- (void) keyDown:(NSEvent *) event {
    
    if ([event keyCode] == 51 || [event keyCode] == 117) {
        if ([[NSApp mainWindow] firstResponder] == fileListView) {
            [self deleteSelectedRowsInTableView:fileListView
                                   ForBaseArray:songs];
        }
        else if ([[NSApp mainWindow] firstResponder] == trackListView) {
            [self deleteSelectedRowsInTableView:trackListView
                                   ForBaseArray:trackItems];
        }
        else if ([[NSApp mainWindow] firstResponder] == resultTableView) {
            [self deleteSelectedRowsInTableView:resultTableView
                                   ForBaseArray:resultList];            
        }
    } 
}

- (void) deleteSelectedRowsInTableView:(NSTableView *) tableView
                          ForBaseArray:(NSMutableArray *) baseArray {
    
    int row = [tableView selectedRow];
    
    if (row >= 0) {
        
        NSEnumerator *theEnum = [tableView selectedRowEnumerator];
        
        id theItem;
        while ((theItem = [theEnum nextObject]) != nil )
        {
            int row = [theItem intValue];
            [baseArray replaceObjectAtIndex:row withObject:[NSNull null]];
        }
        
        [baseArray removeObjectIdenticalTo:[NSNull null]];
        [self refreshTableView:tableView];
    }
}

- (NSMutableArray *) trackItems {
    return trackItems;
}

- (NSMutableArray *) songs {
    return songs;
}

- (NSMutableArray *) resultList {
    return resultList;
}

- (void)controlTextDidChange:(NSNotification *) aNotification {
    [self updatePreview];
}

- (void)controlTextDidEndEditing:(NSNotification *)obj {
    [self saveRenamingMask];
}

- (void) updatePreview {
    FileHandler *fileHandler = [[FileHandler alloc] init];
    NSString *previewText = [fileHandler buildPreviewForFiles:[self songs]
                                                   WithSchema:[renaming stringValue]];
    [fileHandler release];
    if (previewText != nil) [preview setStringValue:previewText];    
}

- (void) clearTableViewsAndDataSources {
    
    // Arrays zurücksetzen
    resultList = nil;
    trackItems = nil;
    
    // TableViews neu laden
    [self refreshTableView:resultTableView];
    [self refreshTableView:trackListView];
    
    [self clearDetails];
}

- (void) clearDetails {
    
    // Details zurücksetzen
    [cover setImage:nil];
}

- (void) refreshTableView:(NSTableView *) tableView {
    
    // Neuladen des TableViews
    [tableView reloadData];
    [tableView setNeedsDisplay];
    [tableView deselectAll:nil];
}

- (void) generateNewTrackItem:(NSString *) trackName
                  trackArtist:(NSString *) newArtist {

    if (trackItems == nil) {
        trackItems = [[NSMutableArray alloc] init];
    }
    
    Track *newTrack = [[Track alloc] init];
    newTrack.title = trackName;
    newTrack.artist = newArtist;
    [trackItems addObject:newTrack];
    [newTrack release];
    [self refreshTableView:trackListView];
    [self clearAdditionalDetails];

}

- (void) generateNewAlbumEntryForArtist:(NSString *) artistName
                                  Album:(NSString *) albumName
                                   Year:(NSString *) year
                                  Genre:(NSString *) genreName
                                  Label:(NSString *) labelName
                                 Tracks:(NSString *) trackNumber
                                  Discs:(NSString *) discNumber; {

    if (resultList == nil) {
        resultList = [[NSMutableArray alloc] init];
    }
    
    // Neuen ResultEntry erzeugen
    ResultEntry *resultEntry = [[ResultEntry alloc] init];
    
    // Werte setzen
    [resultEntry setArtistName:artistName];
    [resultEntry setAlbumName:albumName];
    [resultEntry setYearOfPublication:year];
    [resultEntry setGenre:genreName];
    [resultEntry setLabel:labelName];
    [resultEntry setNumberOfTracks:[trackNumber intValue]];
    [resultEntry setNumberOfDiscs:[discNumber intValue]];
    
    // ResultEntry zur Liste hinzufügen
    [resultList addObject:resultEntry];
    [self refreshTableView:resultTableView];
    [self clearAdditionalDetails];
    [resultEntry release];
}

- (void) clearAdditionalDetails {

    [artist setStringValue:@""];
    [album setStringValue:@""];
    [publicationYear setStringValue:@""];
    [genre setStringValue:@""];
    [label setStringValue:@""];
    [tracks setStringValue:@""];
    [discs setStringValue:@""];
    [trackTitle setStringValue:@""];
    
}

- (void) writeTagsFromFilesInTableViews {

    // Tags auslesen und die TableViews füllen, sofern dort noch keine Informationen eingetragen sind
    if ((![resultList count] > 0) && (![trackItems count] > 0)) {
        
        bool isAlbumName = NO;
        bool isCover = NO;
        int i;
        
        // Tags aller Lieder auslesen
        for (i = 0; i < [songs count]; i++) {
                        
            FileHandler *file = [[[FileHandler alloc] init] autorelease];
            NSMutableDictionary *tags = [file readMetaDataFromMp3File:[songs objectAtIndex:i]];
                        
            if (!isAlbumName && ([tags count] > 0)) {
                
                NSString *artistAlbum = [tags objectForKey:STArtistAlbumTag];
                NSString *albumName = [tags objectForKey:STAlbumTag];
                
                [self generateNewAlbumEntryForArtist:artistAlbum
                                               Album:albumName
                                                Year:[tags objectForKey:STYearTag]
                                               Genre:[tags objectForKey:STGenreTag]
                                               Label:[tags objectForKey:STLabelTag]
                                              Tracks:[tags objectForKey:STTrackCountTag]
                                               Discs:[tags objectForKey:STDiscsTag]];                

                NSString *search = [NSString stringWithFormat:@" %@ %@",artistAlbum, albumName];
                [keywords setStringValue:search];
                isAlbumName = YES;
            }
            
            if (!isCover) {
                NSImage *image = [tags objectForKey:STCoverTag];
                if (image != nil) {
                    [cover setImage:image];
                    isCover = YES;
                }
            }
            
            if ([tags count] > 0) {
                NSString *artistTrack = [tags objectForKey:STArtistTrackTag];
                [self generateNewTrackItem:[tags objectForKey:STTitleTag] trackArtist:artistTrack];
            }
            else {
                break;
            }
        }
    }
}

- (void) saveRenamingMask {
    
    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
    NSString *renamingMask = [renaming stringValue];
    [defaults setObject:renamingMask forKey:STRenamingMaskKey];
    
}

- (void) switchStatus:(NSString *)newStatus {

    [status setStringValue:newStatus];
    [statusView display];
    
}

- (void) handleStatusChange:(NSNotification *) note {

    NSString *newStatus = [[note userInfo] objectForKey:@"status"];
    [self switchStatus:newStatus];
    
}

@end
